﻿/*
 * Copyright 2011 Edward L. Stauff.                           Contact: <EdStauff@gmail.com>.
 * 
 * This file, "XerialClassMapping.cs", is part of the "Stauffware.Common.Xml.Xerialization" 
 * library.  "Stauffware.Common.Xml.Xerialization" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common.Xml.Xerialization" is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * "Stauffware.Common.Xml.Xerialization".  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Reflection;
using Stauffware.Common.Core;

namespace Stauffware.Common.Xml.Xerialization
{
	/// <summary>
	/// Provides a mapping between XML and a .Net class.
	/// </summary>
	public class XerialClassMapping : XerialTypeMapping
	{
		/// <inherit/>
		public override bool AllowsXmlElmnt { get { return true; } }
		/// <inherit/>
		public override bool AllowsXmlAttr { get { return false; } }
		/// <inherit/>
		public override bool AllowsXmlText { get { return false; } }

		/// <summary>
		/// The type mapping for the target type's base class, or null.
		/// </summary>
		public XerialClassMapping BaseClassMapping { get { return this._baseClassMapping; } }
		private XerialClassMapping _baseClassMapping;

		/// <summary>
		/// Contains one XerialAttrMember for each member of the target type (class)
		/// to be xerialized as an XML attribute.
		/// </summary>
		protected readonly Dictionary<string, XerialMemberMapping> AttrMemberMappings;

		/// <summary>
		/// Contains one XerialElmntMember for each member of the target type (class)
		/// to be xerialized as an XML child element.
		/// </summary>
		protected readonly Dictionary<string, XerialMemberMapping> ElmntMemberMappings;

		/// <summary>
		/// The one-and-only TextMemberMapping, or null.
		/// </summary>
		protected TextMemberMapping TextMemberMapping;

		/// <summary>
		/// Specifies whether to ignore unknown XML elements for this class.
		/// </summary>
		protected bool IgnoreUnknownElements;
	
		/// <summary>
		/// Specifies whether to ignore unknown XML attributes for this class.
		/// </summary>
		protected bool IgnoreUnknownAttributes;

		// parameterless constructor for the class, or null if it doesn't have one
		private ConstructorInfo Constructor;

		// factory method for the (abstract) class, or null if it doesn't have one
		private MethodInfo Factory;

		#region Building the graph of Xerial mappings

		//-----------------------------------------------------------------------------
		/// <summary> Constructor. </summary>

		public XerialClassMapping (XerialContext context, Type targetType, string xmlName)
			: base(context, targetType, xmlName, XmlType.Element)
		{
			this.AttrMemberMappings = new Dictionary<string, XerialMemberMapping>(
						context.IgnoreXmlCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);
			this.ElmntMemberMappings = new Dictionary<string, XerialMemberMapping>(
						context.IgnoreXmlCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);

			this.Factory = this.GetFactoryMethod(targetType);

			if (this.Factory == null)
			{
				this.Constructor = this.TargetType.GetConstructor(
						BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
													null, System.Type.EmptyTypes, null);

				// NOTE: we can instantiate an uninitialized object by calling
				//		System.Runtime.Serialization.FormatterServices.GetUninitializedObject,
				//		but the resulting object will have all its fields initialized to zero;
				//		no field initialization will happen.  I don't want to offer this option
				//		without making the user explicitly ask for it.

				if (this.Constructor == null && !this.TargetType.IsValueType)
					throw new InputRequirement(targetType.FullName + 
						" cannot be xerialized because it has no parameterless constructor");
			}

		}

		//-----------------------------------------------------------------------------
		// Returns the XerialFactory method for the given type, or null if it doesn't have one.

		private MethodInfo GetFactoryMethod (Type targetType)
		{
			foreach (MethodInfo m in targetType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
			{
				object[] attrs = m.GetCustomAttributes(typeof(XerialFactory), false);
				if (attrs != null && attrs.Length > 0)
					return m;
			}

			return null;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Builds the network (graph) of supporting mapping objects that this class  
		/// mapping needs to xerialize its members.
		/// </summary>
		/// <remarks>
		/// This is recursive: BuildXerialGraph instantiates subclasses of XerialMemberMapping, 
		/// whose constructors call XerialContext.GetOrAdd, which in turn calls
		/// BuildXerialGraph.  There are additional recursion paths as well.
		/// </remarks>

		public virtual void BuildXerialGraph (XerialTypeAttribute classAttr)
		{
			// first deal with our base class

			if (this.TargetType.BaseType.IsClass) // && 
			//	this.TargetType.BaseType != typeof(System.Object) &&
			//	this.TargetType.BaseType != typeof(System.ValueType))
			{
				this._baseClassMapping = (XerialClassMapping)
						this.Context.TypeMappings.GetOrAdd(this.TargetType.BaseType);
				if (this._baseClassMapping != null)
					this.TextMemberMapping = this._baseClassMapping.TextMemberMapping;
			}

			if (classAttr != null && classAttr.IgnoreUnknownElements.HasValue)
				this.IgnoreUnknownElements = classAttr.IgnoreUnknownElements.Value;
			else this.IgnoreUnknownElements = this.Context.DefaultIgnoreUnknownElements;

			if (classAttr != null && classAttr.IgnoreUnknownAttributes.HasValue)
				this.IgnoreUnknownAttributes = classAttr.IgnoreUnknownAttributes.Value;
			else this.IgnoreUnknownAttributes = this.Context.DefaultIgnoreUnknownAttributes;

			BindingFlags bFlags = BindingFlags.Instance | BindingFlags.Public | 
								  BindingFlags.NonPublic | BindingFlags.DeclaredOnly;

			// collect the fields to be xerialized

			foreach (FieldInfo fi in this.TargetType.GetFields(bFlags))
			{
				XerialMemberAttribute memberAttr = XerialMemberAttribute.GetFrom(fi);

				// determine whether to include this field
				if (!this.IncludeMember(fi, memberAttr, classAttr))
					continue;

				MemberValueEncoding encoding = this.GetEffectiveEncoding(classAttr, fi, memberAttr);
				Assert.Condition(encoding != MemberValueEncoding.Default && encoding != MemberValueEncoding.Automatic);
				if (encoding == MemberValueEncoding.Omit)
					continue;

				// create a XerialMemberMapping

				string xmlName = (memberAttr != null && memberAttr.XmlName != null) ? 
										memberAttr.XmlName : CreateDefaultXmlName(fi.Name);
				bool isOpt = this.MemberIsOptional(memberAttr, classAttr);

				switch (encoding)
				{
					case MemberValueEncoding.Attribute: this.AttrMemberMappings.Add(xmlName,
						new AttributeMemberMapping(this.Context, xmlName, fi, isOpt)); break;

					case MemberValueEncoding.Text: 
						if (this.TextMemberMapping != null)
							throw new XerialException("class " + this.TargetType.FullName + 
										"already has a member whose MemberValueEncoding is Text");
						this.TextMemberMapping = new TextMemberMapping(this.Context, xmlName, fi, isOpt); 
						break;

					case MemberValueEncoding.NestedElements: this.ElmntMemberMappings.Add(xmlName,
						new NestedElements_MemberMapping(this.Context, xmlName, fi, isOpt)); break;

					case MemberValueEncoding.ImplicitValueType: this.ElmntMemberMappings.Add(xmlName,
						new ImplicitType_MemberMapping(this.Context, xmlName, fi, isOpt)); break;

					case MemberValueEncoding.ImplicitMemberName: this.ElmntMemberMappings.Add(xmlName,
						new ImplicitName_MemberMapping(this.Context, xmlName, fi, isOpt)); break;

					case MemberValueEncoding.CustomAttribute: this.AttrMemberMappings.Add(xmlName,
						new CustomMemberMapping(this.Context, xmlName, fi, encoding, isOpt,
												memberAttr.ReadMethod, memberAttr.WriteMethod)); break;

					default: Assert.NotReached("bogus encoding: " + encoding.ToString()); break;
				}
			}

			// collect the properties to be xerialized

			foreach (PropertyInfo pi in this.TargetType.GetProperties(bFlags))
			{
				XerialMemberAttribute memberAttr = XerialMemberAttribute.GetFrom(pi);

				// determine whether to include this property
				if (!this.IncludeMember(pi, memberAttr, classAttr))
					continue;

				MemberValueEncoding encoding = this.GetEffectiveEncoding(classAttr, pi, memberAttr);
				Assert.Condition(encoding != MemberValueEncoding.Default && encoding != MemberValueEncoding.Automatic);
				if (encoding == MemberValueEncoding.Omit)
					continue;

				// create a XerialMemberMapping

				string xmlName = (memberAttr != null && memberAttr.XmlName != null) ? 
									memberAttr.XmlName : CreateDefaultXmlName(pi.Name);
				bool isOpt = this.MemberIsOptional(memberAttr, classAttr);

				switch (encoding)
				{
					case MemberValueEncoding.Attribute: this.AttrMemberMappings.Add(xmlName,
						new AttributeMemberMapping(this.Context, xmlName, pi, isOpt)); break;

					case MemberValueEncoding.Text:
						if (this.TextMemberMapping != null)
							throw new XerialException("class " + this.TargetType.FullName + 
								"already has a member whose MemberValueEncoding is Text");
						this.TextMemberMapping = new TextMemberMapping(this.Context, xmlName, pi, isOpt); 
						break;

					case MemberValueEncoding.NestedElements: this.ElmntMemberMappings.Add(xmlName,
						new NestedElements_MemberMapping(this.Context, xmlName, pi, isOpt)); break;

					case MemberValueEncoding.ImplicitValueType: this.ElmntMemberMappings.Add(xmlName,
						new ImplicitType_MemberMapping(this.Context, xmlName, pi, isOpt)); break;

					case MemberValueEncoding.ImplicitMemberName: this.ElmntMemberMappings.Add(xmlName,
						new ImplicitName_MemberMapping(this.Context, xmlName, pi, isOpt)); break;

					case MemberValueEncoding.CustomAttribute: this.AttrMemberMappings.Add(xmlName,
						new CustomMemberMapping(this.Context, xmlName, pi, encoding, isOpt,
												memberAttr.ReadMethod, memberAttr.WriteMethod)); break;

					default: Assert.NotReached("bogus encoding: " + encoding.ToString()); break;
				}
			}

			// some final validation

			if (this.TextMemberMapping != null && this.ElmntMemberMappings.Count > 0)
				throw new XerialException("A class cannot have a member whose MemberValueEncoding is Text " + 
										  "and any members encoded as elements (" + this.TargetType.FullName + ").");
		}

		//-----------------------------------------------------------------------------

		private bool IncludeMember (FieldInfo fi, XerialMemberAttribute memberAttr, XerialTypeAttribute classAttr)
		{
			if (memberAttr != null)
				return memberAttr.Encoding != MemberValueEncoding.Omit;
			if (classAttr != null)
				return classAttr.IncludeFields.Includes(fi);
			return this.Context.DefaultFieldInclusion.Includes(fi);
		}

		//-----------------------------------------------------------------------------

		private bool IncludeMember (PropertyInfo pi, XerialMemberAttribute memberAttr, XerialTypeAttribute classAttr)
		{
			if (memberAttr != null)
				return memberAttr.Encoding != MemberValueEncoding.Omit;
			if (classAttr != null)
				return classAttr.IncludeProperties.Includes(pi);
			return this.Context.DefaultPropertyInclusion.Includes(pi);
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns the effective encoding for the given field.
		/// </summary>

		private MemberValueEncoding GetEffectiveEncoding (XerialTypeAttribute classAttr,
										FieldInfo fi, XerialMemberAttribute memberAttr)
		{
			if (memberAttr != null)
			{
				if (memberAttr.Encoding == MemberValueEncoding.Automatic)
					return this.Context.GetAutomaticEncoding(fi.FieldType);
				if (memberAttr.Encoding != MemberValueEncoding.Default)
				{
					this.ValidateEncoding(fi, memberAttr, memberAttr.Encoding);
					return memberAttr.Encoding;
				}
			}

			if (classAttr != null)
			{
				// if the class specifies a non-default encoding for fields, then use that
				if (classAttr.FieldEncoding != MemberValueEncoding.Default)
				{
					this.ValidateEncoding(fi, memberAttr, classAttr.FieldEncoding);
					return classAttr.FieldEncoding;
				}
			}

			if (this.Context.DefaultFieldEncoding != MemberValueEncoding.Automatic &&
				this.Context.DefaultFieldEncoding != MemberValueEncoding.Default)
				return this.Context.DefaultFieldEncoding;

			// use the automatic encoding for the field type
			MemberValueEncoding mve = this.Context.GetAutomaticEncoding(fi.FieldType);
			this.ValidateEncoding(fi, memberAttr, mve);
			return mve;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns the effective encoding for the given property.
		/// </summary>

		private MemberValueEncoding GetEffectiveEncoding (XerialTypeAttribute classAttr,
										PropertyInfo pi, XerialMemberAttribute memberAttr)
		{
			if (memberAttr != null)
			{
				if (memberAttr.Encoding == MemberValueEncoding.Automatic)
					return this.Context.GetAutomaticEncoding(pi.PropertyType);
				if (memberAttr.Encoding != MemberValueEncoding.Default)
				{
					this.ValidateEncoding(pi, memberAttr, memberAttr.Encoding);
					return memberAttr.Encoding;
				}
			}

			if (classAttr != null)
			{
				// if the class specifies a non-default encoding for fields, then use that
				if (classAttr.PropertyEncoding != MemberValueEncoding.Default)
				{
					this.ValidateEncoding(pi, memberAttr, classAttr.PropertyEncoding);
					return classAttr.PropertyEncoding;
				}
			}

			if (this.Context.DefaultPropertyEncoding != MemberValueEncoding.Automatic &&
				this.Context.DefaultPropertyEncoding != MemberValueEncoding.Default)
				return this.Context.DefaultPropertyEncoding;

			// use the automatic encoding for the property type
			MemberValueEncoding mve = this.Context.GetAutomaticEncoding(pi.PropertyType);
			this.ValidateEncoding(pi, memberAttr, mve);
			return mve;
		}

		//-----------------------------------------------------------------------------

		private void ValidateEncoding (MemberInfo mInfo, XerialMemberAttribute memberAttr, MemberValueEncoding encoding)
		{
			Require.Condition(encoding != MemberValueEncoding.Default, "should have been resolved by now");

			if (encoding == MemberValueEncoding.CustomContent ||
				encoding == MemberValueEncoding.CustomAttribute)
			{
				if (memberAttr == null || memberAttr.ReadMethod == null || memberAttr.WriteMethod == null)
					throw new XerialException(mInfo.DeclaringType.Name + "." + mInfo.Name + 
						" has an effective MemberValueEncoding of " + encoding.ToString() +
						", therefore you must provide its " + memberAttr.GetType().Name + 
						" with a ReadMethod and a WriteMethod");
			}
			else if (memberAttr != null && (memberAttr.ReadMethod != null || memberAttr.WriteMethod != null))
				throw new XerialException("the use of ReadMethod and WriteMethod in " + memberAttr.GetType().Name + 
						" requires that the effective MemberValueEncoding be CustomElement or CustomAttribute (" +
						mInfo.DeclaringType.Name + "." + mInfo.Name + ")");
		}

		//-----------------------------------------------------------------------------

		private bool MemberIsOptional (XerialMemberAttribute memberAttr, XerialTypeAttribute classAttr)
		{
			//if (memberAttr != null && memberAttr.IsOptional.HasValue)
			//    return memberAttr.IsOptional.Value;
			if (memberAttr != null && memberAttr.IsOptionalWasSet)
				return memberAttr.IsOptional;
			if (classAttr != null && classAttr.MembersAreOptional.HasValue)
				return classAttr.MembersAreOptional.HasValue;
			return this.Context.DefaultMemberOptionality;
		}

		//-----------------------------------------------------------------------------

		private static string CreateDefaultXmlName (string memberName)
		{
			// for automatic property backing fields; e.g. "<MyProperty>k__BackingField"
			return memberName.Replace("<", iAcute).Replace(">", iGrave);
		}

		// chosen for their appearance
		private const string iAcute = "\xED";
		private const string iGrave = "\xEC";

		//-----------------------------------------------------------------------------

		#endregion

		#region Writing to XML

		//-----------------------------------------------------------------------------

		/// <inherit/>
		public override void WriteToXmlElmnt (string name, object targetObj, XmlWriter writer)
		{
			if (targetObj == null)
				return;
			
			writer.WriteStartElement(name == null ? this.XmlName : name);

			this.WriteAttributeMembers(targetObj, writer);

			if (this.TextMemberMapping != null)
				this.TextMemberMapping.WriteToXml(targetObj, writer);
			else this.WriteChildElementMembers(targetObj, writer);

			writer.WriteEndElement();
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Writes the member values of the given object that are mapped to XML attributes.
		/// </summary>

		protected virtual void WriteAttributeMembers (object targetObj, XmlWriter writer)
		{
			if (this.BaseClassMapping != null)
				this.BaseClassMapping.WriteAttributeMembers(targetObj, writer);
			foreach (XerialMemberMapping atMem in this.AttrMemberMappings.Values)
				atMem.WriteToXml(targetObj, writer);
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Writes the member values of the given object that are mapped to XML child elements.
		/// </summary>

		protected virtual void WriteChildElementMembers (object targetObj, XmlWriter writer)
		{
			if (this.BaseClassMapping != null)
				this.BaseClassMapping.WriteChildElementMembers(targetObj, writer);
			foreach (XerialMemberMapping elMem in this.ElmntMemberMappings.Values)
				elMem.WriteToXml(targetObj, writer);
		}

		//-----------------------------------------------------------------------------


		//-----------------------------------------------------------------------------

		#endregion

		#region Reading from XML

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Reads an object from one complete XML element (which may include attributes, 
		/// child elements, and/or text).
		/// </summary>
		/// <param name="reader">
		///		Where to read the XML from.
		///		On entry, the current node must be an Element.
		///		On exit, the current node will be the node immediately following the Element
		///			(for an empty Element) or the corresponding EndElement (for a non-empty element).
		/// </param>
		/// <returns>the newly created object</returns>

		public override object ReadFromXmlElmnt (NiceXmlReader reader)
		{
			if (DebugLogEnabled) this.LogCall("ReadFromXmlElmnt", reader);

			reader.MoveToContent();

			object newObj = this.CreateNewTargetObject(reader);

			bool isEmptyElmnt = reader.IsEmptyElement;	// need to get this before calling ReadAttributes
			string actualElementName = reader.Name;

			List<XerialMemberMapping> missingAttrMembers = 
							new List<XerialMemberMapping>(this.AttrMemberMappings.Values);

			if (reader.MoveToFirstAttribute())
			{
				do
				{
					XerialMemberMapping atMem = this.GetAttrMemberMapping(reader.Name);
					if (atMem != null)
					{
						atMem.ReadFromXml(reader, newObj);
						missingAttrMembers.Remove(atMem);
					}
					else if (!this.IgnoreUnknownAttributes)
						reader.BadXml(ErrorUtils.ValueToString(reader.Name) + 
										" is not a valid attribute for <" + this.XmlName + ">");
				} while (reader.MoveToNextAttribute());

				reader.MoveToElement();		// move back to the element
			}

			List<XerialMemberMapping> missingElmntMembers = 
							new List<XerialMemberMapping>(this.ElmntMemberMappings.Values);


			if (!isEmptyElmnt)
			{
				reader.Read();	// step inside the parent element
				reader.MoveToContent();

				if (this.TextMemberMapping != null)
					this.TextMemberMapping.ReadFromXml(reader, newObj);
				else
				{
					while (reader.NodeType != XmlNodeType.EndElement)
					{
					//	Assert.Condition(reader.NodeType == XmlNodeType.Element);
						if (reader.NodeType != XmlNodeType.Element)
						{
							reader.BadXml("invalid XML content: " + reader.NodeType.ToString() + 
															"; expecting a child element");
							reader.Skip();	// skip this element and all its children
							continue;
						}

						XerialMemberMapping elMem = this.GetElementMemberMapping(reader.Name);
						if (elMem == null)
						{
							if (!this.IgnoreUnknownElements)
								reader.BadXml(ErrorUtils.ValueToString(reader.Name) + 
											" is not a valid child element for <" + this.XmlName + ">");
							reader.Skip();	// skip this element and all its children
						}
						else
						{
							elMem.ReadFromXml(reader, newObj);
							missingElmntMembers.Remove(elMem);
						}
					}
				}

				reader.MoveToContent();
				reader.ExpectEndElement(actualElementName);
			}

			reader.Read();	// skip over EndElement or empty Element node

			return newObj;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a new object for the data at the current position of the given reader.
		/// </summary>

		protected object CreateNewTargetObject (NiceXmlReader reader)
		{
			if (DebugLogEnabled) this.LogCall("CreateNewTargetObject");

			if (this.Factory != null)
			{
				return this.Factory.Invoke(null, new object[] { this.Context, reader });
			}
			else if (this.Constructor != null)
				return this.Constructor.Invoke(null);	// instantiate an object of that class
			else
			{
				// WARNING: when using this call, not only will no constructor get called,
				//	but no field initialization will happen, either.
				object targetObj = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(this.TargetType);
				return targetObj;
			}
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a member mapping for the given XML attribute name, or null
		/// if it can't find one.
		/// </summary>

		protected virtual XerialMemberMapping GetAttrMemberMapping (string xmlAttrName)
		{
			XerialMemberMapping ep;
			if (this.AttrMemberMappings.TryGetValue(xmlAttrName, out ep))
				return ep;
			if (this.BaseClassMapping != null)
				return this.BaseClassMapping.GetAttrMemberMapping(xmlAttrName);
			return null;
		}

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns a member mapping for the given XML element name, or null
		/// if it can't find one.
		/// </summary>

		protected virtual XerialMemberMapping GetElementMemberMapping (string xmlElementName)
		{
			XerialMemberMapping ep;
			if (this.ElmntMemberMappings.TryGetValue(xmlElementName, out ep))
				return ep;
			if (this.BaseClassMapping != null)
				return this.BaseClassMapping.GetElementMemberMapping(xmlElementName);
			return null;
		}

		//-----------------------------------------------------------------------------

		#endregion

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Returns whether this.TargetType is or derives from that.TargetType.
		/// </summary>

		public override bool IsOrDerivesFrom (XerialTypeMapping that)
		{
			for (XerialClassMapping cp = this; cp != null; cp = cp.BaseClassMapping)
				if (cp == that)
					return true;
			return false;
		}

		//-----------------------------------------------------------------------------

		/// <inherit/>
		public override void Dump (System.IO.TextWriter tw)
		{
			base.Dump(tw);

			string indent = "    ";
			
			tw.WriteLine(indent + "ignore unknown: elements=" + this.IgnoreUnknownElements.ToString() +
 										", attributes=" + this.IgnoreUnknownAttributes.ToString());

			foreach (XerialMemberMapping mm in this.AttrMemberMappings.Values)
				mm.Dump(tw, indent);

			foreach (XerialMemberMapping mm in this.ElmntMemberMappings.Values)
				mm.Dump(tw, indent);

			if (this.TextMemberMapping != null)
				this.TextMemberMapping.Dump(tw, indent);
		}

		//-----------------------------------------------------------------------------
	}
}
